using System;
using AndyPike.Commons.TestSupport;
using AndyPike.Snowflake.Core.Exceptions;
using AndyPike.Snowflake.Core.Messages;
using AndyPike.Snowflake.Core.Services;
using AndyPike.Snowflake.Web.Controllers;
using AndyPike.Snowflake.Web.Enums;
using NUnit.Framework;
using NUnit.Framework.SyntaxHelpers;
using Rhino.Mocks;

namespace AndyPike.Snowflake.Web.Tests.Unit.Controllers
{
    [TestFixture]
    public class When_registering_an_account : ControllerUnitTestBase
    {
        private MockRepository mockery;
        private IAccountService accountService;
        private AccountController controller;

        [SetUp]
        public void SetUp()
        {
            mockery = new MockRepository();
            accountService = mockery.DynamicMock<IAccountService>();

            controller = new AccountController(accountService);
            PrepareController(controller, "Account");
        }

        [Test]
        public void Should_render_the_register_form_view()
        {
            controller.New();

            Assert.That(controller.SelectedViewName, Is.EqualTo(@"Account\New"));
        }

        [Test]
        public void Should_create_a_new_account_if_the_inputs_are_valid()
        {
            var authenticationToken = Guid.NewGuid();
            var registrationMessage = new RegistrationMessage { UserName = "andypike", Password = "123456", Email = "someone@somewhere.com" };

            With.Mocks(mockery)
                .Expecting(() => Expect.Call(accountService.RegisterAccount(registrationMessage)).Return(authenticationToken))
                .Verify(() => controller.Create(registrationMessage, true));

            Assert.That(Response.RedirectedTo, Is.EqualTo(@"/My/Dashboard.rails"));
            Assert.That(Cookies["authenticationToken"].Value, Is.EqualTo(authenticationToken.ToString()));
        }

        [Test]
        public void Should_redirect_back_to_the_register_form_with_an_error_if_the_user_fails_the_captcha_test()
        {
            var registrationMessage = new RegistrationMessage { UserName = "andypike", Password = "123456", Email = "someone@somewhere.com" };

            controller.Create(registrationMessage, false);

            Assert.That(Response.RedirectedTo, Is.EqualTo(@"/Account/New.rails"));
            Assert.That(controller.Flash["error"], Is.EqualTo(Error.FailedCaptcha));
            Assert.That(controller.Flash["registration"], Is.EqualTo(registrationMessage));
            Assert.That(registrationMessage.Password, Is.EqualTo(string.Empty));
        }

        [Test]
        public void Should_redirect_back_to_the_register_form_with_an_error_if_the_user_name_is_not_unique()
        {
            AccountServiceThrowsException(new DuplicateUserNameException(), Error.DuplicateUserName);
        }

        [Test]
        public void Should_redirect_back_to_the_register_form_with_an_error_if_the_user_name_is_invalid()
        {
            AccountServiceThrowsException(new InvalidUserNameException(), Error.InvalidUserName);
        }

        [Test]
        public void Should_redirect_back_to_the_register_form_with_an_error_if_the_password_is_invalid()
        {
            AccountServiceThrowsException(new InvalidPasswordException(), Error.InvalidPassword);
        }

        [Test]
        public void Should_redirect_back_to_the_register_form_with_an_error_if_the_email_is_invalid()
        {
            AccountServiceThrowsException(new InvalidEmailException(), Error.InvalidEmail);
        }

        private void AccountServiceThrowsException(Exception exception, Error error)
        {
            var registrationMessage = new RegistrationMessage { UserName = "andypike", Password = "123456", Email = "someone@somewhere.com" };

            With.Mocks(mockery)
                .Expecting(() => Expect.Call(accountService.RegisterAccount(registrationMessage)).Throw(exception))
                .Verify(() => controller.Create(registrationMessage, true));

            Assert.That(Response.RedirectedTo, Is.EqualTo(@"/Account/New.rails"));
            Assert.That(controller.Flash["error"], Is.EqualTo(error));
            Assert.That(controller.Flash["registration"], Is.EqualTo(registrationMessage));
            Assert.That(registrationMessage.Password, Is.EqualTo(string.Empty));
        }
    }

    [TestFixture]
    public class When_logging_out_from_the_site : ControllerUnitTestBase
    {
        private MockRepository mockery;
        private IAccountService accountService;
        private AccountController controller;

        [SetUp]
        public void SetUp()
        {
            mockery = new MockRepository();
            accountService = mockery.DynamicMock<IAccountService>();

            controller = new AccountController(accountService);
            PrepareController(controller, "Account");
        }

        [Test]
        public void Should_remove_the_authentication_cookie_and_redirect_back_to_the_homepage()
        {
            Response.CreateCookie("authenticationToken", "An authentication cookie", DateTime.Now.AddYears(1));

            controller.Logout();

            Assert.That(Cookies.ContainsKey("authenticationToken"), Is.False);
            Assert.That(Response.RedirectedTo, Is.EqualTo(@"/Account/LoggedOut.rails"));
        }

        [Test]
        public void Should_render_the_logged_out_view()
        {
            controller.LoggedOut();

            Assert.That(controller.SelectedViewName, Is.EqualTo(@"Account\LoggedOut"));
        }
    }

    [TestFixture]
    public class When_logging_into_the_site : ControllerUnitTestBase
    {
        private MockRepository mockery;
        private IAccountService accountService;
        private AccountController controller;

        [SetUp]
        public void SetUp()
        {
            mockery = new MockRepository();
            accountService = mockery.DynamicMock<IAccountService>();

            controller = new AccountController(accountService);
            PrepareController(controller, "Account");
        }

        [Test]
        public void Should_render_the_login_view()
        {
            controller.Login();

            Assert.That(controller.SelectedViewName, Is.EqualTo(@"Account\Login"));
        }

        [Test]
        public void Should_authenticate_valid_credentials_and_store_the_authentication_cookie()
        {
            var authenticationToken = Guid.NewGuid();
            var credentials = new CredentialsMessage { UserName = "andypike", Password = "123456"};

            With.Mocks(mockery)
                .Expecting(() => Expect.Call(accountService.AuthenticateCredentials(credentials)).Return(authenticationToken))
                .Verify(() => controller.Authenticate(credentials));

            Assert.That(Response.RedirectedTo, Is.EqualTo(@"/My/Dashboard.rails"));
            Assert.That(Cookies["authenticationToken"].Value, Is.EqualTo(authenticationToken.ToString()));
        }

        [Test]
        public void Should_redirect_back_to_the_login_form_with_an_error_if_authentication_failed()
        {
            var credentials = new CredentialsMessage { UserName = "andypike", Password = "invalid" };

            With.Mocks(mockery)
                .Expecting(() => Expect.Call(accountService.AuthenticateCredentials(credentials)).Throw(new AuthenticationFailedException()))
                .Verify(() => controller.Authenticate(credentials));

            Assert.That(Response.RedirectedTo, Is.EqualTo(@"/Account/Login.rails"));
            Assert.That(controller.Flash["error"], Is.EqualTo(Error.AuthenticationFailed));
        }
    }
}